<?php
/**
 * SocialEngine
 *
 * @category   Application_Extensions
 * @package    Credit
 * @copyright  Copyright Hire-Experts LLC
 * @license    http://www.hire-experts.com
 * @version    $Id: 2Checkout.php 26.01.12 12:25 TeaJay $
 * @author     Taalay
 */

/**
 * @category   Application_Extensions
 * @package    Credit
 * @copyright  Copyright Hire-Experts LLC
 * @license    http://www.hire-experts.com
 */

class Credit_Plugin_Gateway_2Checkout extends Payment_Plugin_Gateway_2Checkout
{

  protected $_gateway;
  /**
   * Get the gateway object
   *
   * @return Engine_Payment_Gateway
   */
  public function getGateway()
  {
    if( null === $this->_gateway ) {
      $class = 'Credit_Plugin_Payment_Gateway_2Checkout';
      Engine_Loader::loadClass($class);
      $gateway = new $class(array(
        'config' => (array) $this->_gatewayInfo->config,
        'testMode' => $this->_gatewayInfo->test_mode,
        'currency' => Engine_Api::_()->getApi('settings', 'core')->getSetting('payment.currency', 'USD'),
      ));
      if( !($gateway instanceof Engine_Payment_Gateway) ) {
        throw new Engine_Exception('Plugin class not instance of Engine_Payment_Gateway');
      }
      $this->_gateway = $gateway;
    }

    return $this->_gateway;
  }

  public function createCreditTransaction( Credit_Model_Order $co,	array $params = array())
 	{
    // Check that gateways match
    if( $co->gateway_id != $this->_gatewayInfo->gateway_id ) {
      throw new Engine_Payment_Plugin_Exception('Gateways do not match');
    }

    //Get unique orders
    $orders = $co->getPendingOrders();
    if ( $orders->count() <= 0){
      throw new Engine_Payment_Plugin_Exception('CREDIT_No orders found');
    }

    /**
     *
     * Get related info
     *
     * @var $order Payment_Model_Order
     * @var $payment Credit_Model_Payment
     */

 		foreach($orders as $order)
    {
      if ( null == ($payment = $order->getSource())){
        $order->onIncomplete();
        continue;
      }

      // Try to update/create all product if enabled
      $gatewayPlugin = $this->_gatewayInfo->getGateway();
      if( $this->_gatewayInfo->enabled &&
          method_exists($gatewayPlugin, 'createProduct') &&
          method_exists($gatewayPlugin, 'editProduct') &&
          method_exists($gatewayPlugin, 'detailVendorProduct') ) {

        // If it throws an exception, or returns empty, assume it doesn't exist?
        try {
          $info = $gatewayPlugin->detailVendorProduct($payment->getIdentity());
        } catch( Exception $e ) {
          $info = false;
        }
        // Create
        if( !$info ) {
          $gatewayPlugin->createProduct($payment->getPaymentParams());
        }
      }

      // Do stuff to params
      $params['fixed'] = true;
      $params['skip_landing'] = true;

      // Lookup product id for this subscription
      $productInfo = $this->getService()->detailVendorProduct($payment->getIdentity());
      $params['product_id'] = $productInfo['product_id'];
      $params['quantity'] = 1;

      // Create transaction
      $transaction = $this->createTransaction($params);

      return $transaction;
 		}
 	}

  public function onCreditTransactionReturn(Credit_Model_Order $co, array $params = array())
 	{
     // Check that gateways match
     if( $co->gateway_id != $this->_gatewayInfo->gateway_id ) {
       throw new Engine_Payment_Plugin_Exception('Gateways do not match');
     }

     //Get created orders
     $orders = $co->getPendingOrders();
     if ( $orders->count() <= 0){
       throw new Engine_Payment_Plugin_Exception('CREDIT_No orders found');
     }

     /**
      * Get related info
      *
      * @var $user User_Model_User
      * @var $order Payment_Model_Order
      * @var $item Credit_Model_Payment
      * @var $my_order Credit_Model_Order
      * @var $translate Zend_Translate
      * @var $gateway Credit_Plugin_Payment_Gateway_2Checkout //@todo remove for other Gateways
      */

     $my_order = $co;
     $order = $orders[0];
     $user = $order->getUser();
     $item = $order->getSource();

     // Check order states
     if( $order->state == 'completed') {
       return 'completed';
     }

     // Let's log it
     $this->getGateway()->getLog()->log('Return: '
         . print_r($params, true), Zend_Log::INFO);


     // Check for cancel state - the user cancelled the transaction
     if( $params['state'] == 'cancel' ) {
       $this->_onFailure($orders);
       // Error
       throw new Payment_Model_Exception('Your payment has been cancelled and ' .
         'not been purchased. If this is not correct, please try again later.');
     }
     // Check for processed
     if( empty($params['credit_card_processed']) ) {
       // This is a sanity error and cannot produce information a user could use
       // to correct the problem.
       throw new Payment_Model_Exception('There was an error processing your ' .
           'transaction. Please try again later.');
     }
     // Ensure product ids match
     if( $params['merchant_product_id'] != $item->getIdentity() ) {
       // This is a sanity error and cannot produce information a user could use
       // to correct the problem.
       throw new Payment_Model_Exception('There was an error processing your ' .
           'transaction. Please try again later.');
     }
     // Ensure order ids match
     if( $params['order_id'] != $my_order->order_id &&
         $params['merchant_order_id'] != $order->order_id ) {
       // This is a sanity error and cannot produce information a user could use
       // to correct the problem.
       throw new Payment_Model_Exception('There was an error processing your ' .
           'transaction. Please try again later.');
     }
     // Ensure vendor ids match
     if( $params['sid'] != $this->getGateway()->getVendorIdentity() ) {
       // This is a sanity error and cannot produce information a user could use
       // to correct the problem.
       throw new Payment_Model_Exception('There was an error processing your ' .
           'transaction. Please try again later.');
     }

     // Validate return
     try {
       $this->getGateway()->validateReturn($params);
     } catch( Exception $e ) {
       if( !$this->getGateway()->getTestMode() ) {
         // This is a sanity error and cannot produce information a user could use
         // to correct the problem.
         throw new Payment_Model_Exception('There was an error processing your ' .
             'transaction. Please try again later.');
       } else {
         echo $e; // For test mode
       }
     }

     // Update order with profile info and complete status?
     $order->state = 'complete';
     $order->gateway_order_id = $params['order_number'];
     $order->save();

     // Get benefit setting
     $giveBenefit = Engine_Api::_()->getDbtable('transactions', 'payment')
         ->getBenefitStatus($user);

     if( $giveBenefit ) {
      /**
       * @var $transactionsTable Credit_Model_DbTable_Transactions
       */
       $real_price = 0;
       if ($item instanceof Credit_Model_Payment) {
         $real_price = (float) $item->getPrice();
       }

       $transactionsTable = Engine_Api::_()->getDbTable('transactions', 'credit');
       $db = $transactionsTable->getAdapter();
       $db->beginTransaction();

       try {
         $transactionsTable->insert(array(
           'order_id' => $order->getIdentity(),
           'user_id' => $order->user_id,
           'gateway_id' => $this->_gatewayInfo->gateway_id,
           'creation_date' => new Zend_Db_Expr('NOW()'),
           'state' => 'okay',
           'credits' => $item->getCredit(),
           'price' => $real_price,
           'currency' => $this->getGateway()->getCurrency(),
         ));

         $db->commit();
       } catch( Exception $e){
         $db->rollBack();
         throw $e;
       }

       $co->onPaymentSuccess();

       // Give Credits
       /**
        * @var $api Credit_Api_Core
        */
       $credits = $item->getCredit();
       $buyer = Engine_Api::_()->getItem('user', $order->user_id);

       $api = Engine_Api::_()->credit();
       $api->buyCredits($buyer, $credits, '2Checkout');

       return 'completed';
     }

     // Enable later
     else {

       // Update subscription
       $co->gateway_id = $this->_gatewayInfo->gateway_id;
       $co->onPaymentPending();

       // send notification
       if( $co->didStatusChange() ) {
         Engine_Api::_()->getApi('mail', 'core')->sendSystem($user, 'credit_transaction_overdue', array(
           'credit_title' => 'Buying credits',
           'credit_description' => 'Error',
           'object_link' => 'http://' . $_SERVER['HTTP_HOST'] .
             Zend_Controller_Front::getInstance()->getRouter()->assemble(array(), 'user_login', true),
         ));
       }

       return 'pending';
     }
 	}


  public function onIpn(Engine_Payment_Ipn $ipn)
  {
    $rawData = $ipn->getRawData();

    $ordersTable = Engine_Api::_()->getDbtable('orders', 'payment');
    $transactionsTable = Engine_Api::_()->getDbtable('transactions', 'payment');


    // Find transactions -------------------------------------------------------
    $transactionId = null;
    $parentTransactionId = null;
    $transaction = null;
    $parentTransaction = null;

    // Fetch by txn_id
    if( !empty($rawData['txn_id']) ) {
      $transaction = $transactionsTable->fetchRow(array(
        'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
        'gateway_transaction_id = ?' => $rawData['txn_id'],
      ));
      $parentTransaction = $transactionsTable->fetchRow(array(
        'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
        'gateway_parent_transaction_id = ?' => $rawData['txn_id'],
      ));
    }
    // Fetch by parent_txn_id
    if( !empty($rawData['parent_txn_id']) ) {
      if( !$transaction ) {
        $parentTransaction = $transactionsTable->fetchRow(array(
          'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
          'gateway_parent_transaction_id = ?' => $rawData['parent_txn_id'],
        ));
      }
      if( !$parentTransaction ) {
        $parentTransaction = $transactionsTable->fetchRow(array(
          'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
          'gateway_transaction_id = ?' => $rawData['parent_txn_id'],
        ));
      }
    }
    // Fetch by transaction->gateway_parent_transaction_id
    if( $transaction && !$parentTransaction &&
        !empty($transaction->gateway_parent_transaction_id) ) {
      $parentTransaction = $transactionsTable->fetchRow(array(
        'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
        'gateway_parent_transaction_id = ?' => $transaction->gateway_parent_transaction_id,
      ));
    }
    // Fetch by parentTransaction->gateway_transaction_id
    if( $parentTransaction && !$transaction &&
        !empty($parentTransaction->gateway_transaction_id) ) {
      $transaction = $transactionsTable->fetchRow(array(
        'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
        'gateway_parent_transaction_id = ?' => $parentTransaction->gateway_transaction_id,
      ));
    }
    // Get transaction id
    if( $transaction ) {
      $transactionId = $transaction->gateway_transaction_id;
    } else if( !empty($rawData['txn_id']) ) {
      $transactionId = $rawData['txn_id'];
    }
    // Get parent transaction id
    if( $parentTransaction ) {
      $parentTransactionId = $parentTransaction->gateway_transaction_id;
    } else if( $transaction && !empty($transaction->gateway_parent_transaction_id) ) {
      $parentTransactionId = $transaction->gateway_parent_transaction_id;
    } else if( !empty($rawData['parent_txn_id']) ) {
      $parentTransactionId = $rawData['parent_txn_id'];
    }



    // Fetch order -------------------------------------------------------------
    $order = null;

    // Transaction IPN - get order by invoice
    if( !$order && !empty($rawData['invoice']) ) {
      $order = $ordersTable->find($rawData['invoice'])->current();
    }

    // Subscription IPN - get order by recurring_payment_id
    if( !$order && !empty($rawData['recurring_payment_id']) ) {
      // Get attached order
      $order = $ordersTable->fetchRow(array(
        'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
        'gateway_order_id = ?' => $rawData['recurring_payment_id'],
      ));
    }

    // Subscription IPN - get order by rp_invoice_id
    //if( !$order && !empty($rawData['rp_invoice_id']) ) {
    //
    //}

    // Transaction IPN - get order by parent_txn_id
    if( !$order && $parentTransactionId ) {
      $order = $ordersTable->fetchRow(array(
        'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
        'gateway_transaction_id = ?' => $parentTransactionId,
      ));
    }

    // Transaction IPN - get order by txn_id
    if( !$order && $transactionId ) {
      $order = $ordersTable->fetchRow(array(
        'gateway_id = ?' => $this->_gatewayInfo->gateway_id,
        'gateway_transaction_id = ?' => $transactionId,
      ));
    }

    // Transaction IPN - get order through transaction
    if( !$order && !empty($transaction->order_id) ) {
      $order = $ordersTable->find($parentTransaction->order_id)->current();
    }

    // Transaction IPN - get order through parent transaction
    if( !$order && !empty($parentTransaction->order_id) ) {
      $order = $ordersTable->find($parentTransaction->order_id)->current();
    }



    // Process generic IPN data ------------------------------------------------

    // Build transaction info
    if( !empty($rawData['txn_id']) ) {
      $transactionData = array(
        'gateway_id' => $this->_gatewayInfo->gateway_id,
      );
      // Get timestamp
      if( !empty($rawData['payment_date']) ) {
        $transactionData['timestamp'] = date('Y-m-d H:i:s', strtotime($rawData['payment_date']));
      } else {
        $transactionData['timestamp'] = new Zend_Db_Expr('NOW()');
      }
      // Get amount
      if( !empty($rawData['mc_gross']) ) {
        $transactionData['amount'] = $rawData['mc_gross'];
      }
      // Get currency
      if( !empty($rawData['mc_currency']) ) {
        $transactionData['currency'] = $rawData['mc_currency'];
      }
      // Get order/user
      if( $order ) {
        $transactionData['user_id'] = $order->user_id;
        $transactionData['order_id'] = $order->order_id;
      }
      // Get transactions
      if( $transactionId ) {
        $transactionData['gateway_transaction_id'] = $transactionId;
      }
      if( $parentTransactionId ) {
        $transactionData['gateway_parent_transaction_id'] = $parentTransactionId;
      }
      // Get payment_status
      switch( $rawData['payment_status'] ) {
        case 'Canceled_Reversal': // @todo make sure this works

        case 'Completed':
        case 'Created':
        case 'Processed':
          $transactionData['type'] = 'credit_payment';
          $transactionData['state'] = 'okay';
          break;

        case 'Denied':
        case 'Expired':
        case 'Failed':
        case 'Voided':
          $transactionData['type'] = 'credit_payment';
          $transactionData['state'] = 'failed';
          break;

        case 'Pending':
          $transactionData['type'] = 'credit_payment';
          $transactionData['state'] = 'pending';
          break;

        case 'Refunded':
          $transactionData['type'] = 'refund';
          $transactionData['state'] = 'refunded';
          break;
        case 'Reversed':
          $transactionData['type'] = 'reversal';
          $transactionData['state'] = 'reversed';
          break;

        default:
          $transactionData = 'unknown';
          break;
      }

			//@todo Add transaction details to credit owners
			/**
			 * @var $payment Credit_Model_Payment
			 */
			$payment = $order->getSource();

			if ( !$payment->payment_id )
			{
				// Insert new transaction
				if( !$transaction ) {
					$transactionsTable->insert($transactionData);
				}
				// Update transaction
				else {
					unset($transactionData['timestamp']);
					$transaction->setFromArray($transactionData);
					$transaction->save();
				}
			}

      // Update parent transaction on refund?
      if( $parentTransaction && in_array($transactionData['type'], array('refund','reversal')) ) {
        $parentTransaction->state = $transactionData['state'];
        $parentTransaction->save();
      }
    }



    // Process specific IPN data -----------------------------------------------
    if( $order ) {
      // Subscription IPN
      if( $order->source_type == 'credit_payment' ) {
        $this->onCreditTransactionIpn($order, $ipn);
        return $this;
      }
      // Unknown IPN
      else {
        throw new Engine_Payment_Plugin_Exception('Unknown order type for IPN');
      }
    }
    // Missing order
    else {
      throw new Engine_Payment_Plugin_Exception('Unknown or unsupported IPN ' .
          'type, or missing transaction or order ID');
    }
  }

  /**
   * Process ipn of credit transaction
   *
   * @param Payment_Model_Order $order
   * @param Engine_Payment_Ipn $ipn
   */
  public function onCreditTransactionIpn(
      Payment_Model_Order $order,
      Engine_Payment_Ipn $ipn)
  {
    // Check that gateways match
    if( $order->gateway_id != $this->_gatewayInfo->gateway_id ) {
      throw new Engine_Payment_Plugin_Exception('Gateways do not match');
    }

    // Get related info
    $user = $order->getUser();
    $payment = $order->getSource();
    /**
     * @var $my_order Credit_Model_Order
     **/
    $my_order = Engine_Api::_()->getItem('credit_order', $order->gateway_order_id);

    // Get IPN data
    $rawData = $ipn->getRawData();

    // Get tx table
    $transactionsTable = Engine_Api::_()->getDbtable('transactions', 'payment');

    // switch message_type
    switch( $rawData['message_type'] ) {
      case 'ORDER_CREATED':
      case 'FRAUD_STATUS_CHANGED':
      case 'INVOICE_STATUS_CHANGED':
        // Check invoice and fraud status
        if( strtolower($rawData['invoice_status']) == 'declined' ||
            strtolower($rawData['fraud_status']) == 'fail' ) {
          // Payment failure
          $my_order->onPaymentFailure();
          // send notification
          if( $my_order->didStatusChange() ) {
            Engine_Api::_()->getApi('mail', 'core')->sendSystem($user, 'credit_transaction_overdue', array(
              'credit_title' => 'Buying credits',
              'credit_description' => 'Error',
              'object_link' => 'http://' . $_SERVER['HTTP_HOST'] .
                  Zend_Controller_Front::getInstance()->getRouter()->assemble(array(), 'user_login', true),
            ));
          }
        } else if( strtolower($rawData['fraud_status']) == 'wait' ) {

        } else {
          // Payment Success
          $my_order->onPaymentSuccess();
          // send notification
          if( $my_order->didStatusChange() ) {
            Engine_Api::_()->getApi('mail', 'core')->sendSystem($user, 'credit_transaction_success', array(
              'credit_title' => 'Buying credits',
              'credit_description' => 'You have bought some credits from us',
              'object_link' => 'http://' . $_SERVER['HTTP_HOST'] .
                  Zend_Controller_Front::getInstance()->getRouter()->assemble(array(), 'user_login', true),
            ));
          }
        }
        break;

      case 'RECURRING_INSTALLMENT_SUCCESS':
        $my_order->onPaymentSuccess();
        // send notification
        if( $my_order->didStatusChange() ) {
          Engine_Api::_()->getApi('mail', 'core')->sendSystem($user, 'credit_transaction_overdue', array(
            'credit_title' => 'Buying credits',
            'credit_description' => 'You have bought some credits from us',
            'object_link' => 'http://' . $_SERVER['HTTP_HOST'] .
                Zend_Controller_Front::getInstance()->getRouter()->assemble(array(), 'user_login', true),
          ));
        }
        break;

      case 'RECURRING_INSTALLMENT_FAILED':
        $my_order->onPaymentFailure();
        // send notification
        if( $my_order->didStatusChange() ) {
          Engine_Api::_()->getApi('mail', 'core')->sendSystem($user, 'credit_transaction_success', array(
            'credit_title' => 'Buying credits',
            'credit_description' => 'You have bought some credits from us',
            'object_link' => 'http://' . $_SERVER['HTTP_HOST'] .
                Zend_Controller_Front::getInstance()->getRouter()->assemble(array(), 'user_login', true),
          ));
        }
        break;

      case 'RECURRING_STOPPED':
        $my_order->onCancel();
        // send notification
        if( $my_order->didStatusChange() ) {
          Engine_Api::_()->getApi('mail', 'core')->sendSystem($user, 'credit_transaction_success', array(
            'credit_title' => 'Buying credits',
            'credit_description' => 'You have bought some credits from us',
            'object_link' => 'http://' . $_SERVER['HTTP_HOST'] .
                Zend_Controller_Front::getInstance()->getRouter()->assemble(array(), 'user_login', true),
          ));
        }
        break;

      default:
        throw new Engine_Payment_Plugin_Exception(sprintf('Unknown IPN ' .
            'type %1$s', $rawData['message_type']));
        break;
    }

    return $this;
  }

  protected function _onFailure( $orders )
  {
    /**
     * @var $order Payment_Model_Order
     * @var $my_order Credit_Model_Order
     */

    foreach( $orders as $order )
    {
      if( !($order instanceof Payment_Model_Order) || $order->gateway_id != $this->_gatewayInfo->gateway_id ) continue;
      $order->onCancel();

      if ( null == ($payment = $order->getSource())) continue;
      $my_order = Engine_Api::_()->getItem('credit_order', $order->gateway_order_id);
      $my_order->onPaymentFailure();
    }
  }
}
